# Структурные типы

Некоторые варианты использования, такие как моделирование доступа к базе данных, 
более удобны в динамически типизированных языках, чем в статически типизированных языках. 
С динамически типизированными языками естественно моделировать строку как запись или объект 
и выбирать записи с помощью простых точечных обозначений, например `row.columnName`.

Достижение того же результата в статически типизированном языке требует определения класса для каждой возможной строки, 
возникающей в результате манипуляций с базой данных, 
включая строки, возникающие в результате join и проектирования, 
и настройки схемы для сопоставления между строкой и представляющим ее классом.

Это требует большого количества шаблонов, 
что заставляет разработчиков менять преимущества статической типизации на более простые схемы, 
в которых имена столбцов представляются в виде строк и передаются другим операторам, например `row.select("columnName")`. 
Этот подход лишен преимуществ статической типизации и все еще не так естественен, как динамически типизируемая версия.

Структурные типы (structural types) помогают в ситуациях, 
когда желательно поддерживать простую точечную нотацию в динамических контекстах, 
не теряя преимуществ статической типизации. 
Они также позволяют разработчикам настраивать, как должны определяться поля и методы.

### Пример

Вот пример структурного типа `Person`:

```dotty
class Record(elems: (String, Any)*) extends Selectable:
  private val fields = elems.toMap
  def selectDynamic(name: String): Any = fields(name)

type Person = Record {
  val name: String
  val age: Int
}
```

Тип `Person` добавляет уточнение (_refinement_) к своему родительскому типу `Record`, 
которое определяет поля `name` и `age`. 
Говорится, что уточнение носит структурный (_structural_) характер, 
поскольку `name` и `age` не определены в родительском типе. 
Но тем не менее они существуют как члены класса `Person`. 
Например, следующая программа напечатала бы "`Emma is 42 years old.`":

```dotty
val person = Record(
  "name" -> "Emma",
  "age" -> 42
).asInstanceOf[Person]

println(s"${person.name} is ${person.age} years old.")
```

Родительский тип `Record` в этом примере представляет собой универсальный класс, 
который может в своем аргументе `elems` принимать произвольные записи. 
Этот аргумент - последовательность пар ключей типа `String` и значений типа `Any`. 
Когда создается `Person` как `Record`, необходимо с помощью приведения типов задать, 
что запись определяет правильные поля правильных типов. 
Сама `Record` слишком слабо типизирована, поэтому компилятор не может знать об этом без помощи пользователя. 
На практике связь между структурным типом и его базовым общим представлением, скорее всего, 
будет выполняться на уровне базы данных и, следовательно, не будет беспокоить конечного пользователя.

`Record` расширяет маркер trait `scala.Selectable` и определяет метод `selectDynamic`, 
который сопоставляет имя поля с его значением. 
Выбор элемента структурного типа выполняется путем вызова соответствующего метода. 
`person.name` и `person.age` преобразуются компилятором Scala в:

```dotty
person.selectDynamic("name").asInstanceOf[String]
person.selectDynamic("age").asInstanceOf[Int]
```


### Второй пример

Чтобы закрепить сказанное, вот еще один структурный тип с именем `Book`, 
представляющий книгу, доступную в базе данных:

```dotty
type Book = Record {
  val title: String
  val author: String
  val year: Int
  val rating: Double
}
```

Как и в случае с `Person`, экземпляр `Book` создается следующим образом:

```dotty
val book = Record(
  "title" -> "The Catcher in the Rye",
  "author" -> "J. D. Salinger",
  "year" -> 1951,
  "rating" -> 4.5
).asInstanceOf[Book]
```


### Класс Selectable

Помимо `selectDynamic` класс `Selectable` иногда также определяет метод `applyDynamic`, 
который можно использовать для замены вызовов функций на вызов структурных элементов. 
Таким образом, если `a` является экземпляром `Selectable`, структурный вызов типа `a.f(b, c)` преобразуется в:

```dotty
a.applyDynamic("f")(b, c)
```


---

**Ссылки:**

- [Scala3 book](https://docs.scala-lang.org/scala3/book/types-structural.html)
- [Scala 3 Reference](https://docs.scala-lang.org/scala3/reference/changed-features/structural-types.html)
- [Scala 3 Reference, Details](https://docs.scala-lang.org/scala3/reference/changed-features/structural-types-spec.html)
